#pragma once

#include "glare.math.Prerequisites.h"

#include "glare.math.Angles.h"
#include "glare.math.NumericUtils.h"

namespace Glare {

    class AngleUtils 
    {
	public:
       /** The angular units used by the API. This functionality is now deprecated in favor
	       of discreet angular unit types ( see Degree and Radian above ). The only place
		   this functionality is actually still used is when parsing files. Search for
		   usage of the Angle class for those instances
       */
       enum AngleUnit
       {
           AU_DEGREE,
           AU_RADIAN
       };

    protected:
       // angle units used by the api
       static AngleUnit msAngleUnit;

        /// Size of the trig tables as determined by constructor.
        static int mTrigTableSize;

        /// Radian -> index factor value ( mTrigTableSize / 2 * PI )
        static Real mTrigTableFactor;
        static Real* mSinTable;
        static Real* mTanTable;

        /** Private function to build trig tables.
        */
        void buildTrigTables();

        static Real SinTable (Real fValue);
        static Real TanTable (Real fValue);
    public:
        /** Default constructor.
            @param
                trigTableSize Optional parameter to set the size of the
                tables used to implement Sin, Cos, Tan
        */
        AngleUtils(unsigned int trigTableSize = 4096);

        /** Default destructor.
        */
        ~AngleUtils();

        /** Absolute value function
            @param dValue
                The value, in degrees, whose absolute value will be returned.
         */
		static inline Degree Abs (const Degree& dValue) { return Degree(fabs(dValue.valueDegrees())); }

        /** Absolute value function
            @param rValue
                The value, in radians, whose absolute value will be returned.
         */
        static inline Radian Abs (const Radian& rValue) { return Radian(fabs(rValue.valueRadians())); }

        /** Arc cosine function
            @param fValue
                The value whose arc cosine will be returned.
         */
		static Radian ACos (Real fValue);

        /** Arc sine function
            @param fValue
                The value whose arc sine will be returned.
         */
		static Radian ASin (Real fValue);

        /** Arc tangent function
            @param fValue
                The value whose arc tangent will be returned.
         */
		static inline Radian ATan (Real fValue) { return Radian(atan(fValue)); }

        /** Arc tangent between two values function
            @param fY
                The first value to calculate the arc tangent with.
            @param fX
                The second value to calculate the arc tangent with.
         */
		static inline Radian ATan2 (Real fY, Real fX) { return Radian(atan2(fY,fX)); }
		
        /** Cosine function.
            @param fValue
                Angle in radians
            @param useTables
                If true, uses lookup tables rather than
                calculation - faster but less accurate.
        */
        static inline Real Cos (const Radian& fValue, bool useTables = false) {
			return (!useTables) ? Real(cos(fValue.valueRadians())) : SinTable(fValue.valueRadians() + Angle::HALF_PI);
		}
        /** Cosine function.
            @param fValue
                Angle in radians
            @param useTables
                If true, uses lookup tables rather than
                calculation - faster but less accurate.
        */
        static inline Real Cos (Real fValue, bool useTables = false) {
			return (!useTables) ? Real(cos(fValue)) : SinTable(fValue + Angle::HALF_PI);
		}
		
        static inline Radian Sign ( const Radian& rValue )
        {
            return Radian(NumericUtils::Sign(rValue.valueRadians()));
        }

        static inline Degree Sign ( const Degree& dValue )
        {
            return Degree(NumericUtils::Sign(dValue.valueDegrees()));
        }

		/** Sine function.
            @param fValue
                Angle in radians
            @param useTables
                If true, uses lookup tables rather than
                calculation - faster but less accurate.
        */
        static inline Real Sin (const Radian& fValue, bool useTables = false) {
			return (!useTables) ? Real(sin(fValue.valueRadians())) : SinTable(fValue.valueRadians());
		}
        /** Sine function.
            @param fValue
                Angle in radians
            @param useTables
                If true, uses lookup tables rather than
                calculation - faster but less accurate.
        */
        static inline Real Sin (Real fValue, bool useTables = false) {
			return (!useTables) ? Real(sin(fValue)) : SinTable(fValue);
		}

        /** Square root function.
            @param fValue
                The value, in radians, whose square root will be calculated.
            @return
                The square root of the angle in radians.
         */
        static inline Radian Sqrt (const Radian& fValue) { return Radian(sqrt(fValue.valueRadians())); }

        /** Square root function.
            @param fValue
                The value, in degrees, whose square root will be calculated.
            @return
                The square root of the angle in degrees.
         */
        static inline Degree Sqrt (const Degree& fValue) { return Degree(sqrt(fValue.valueDegrees())); }
       
        /** Tangent function.
            @param fValue
                Angle in radians
            @param useTables
                If true, uses lookup tables rather than
                calculation - faster but less accurate.
        */
		static inline Real Tan (const Radian& fValue, bool useTables = false) {
			return (!useTables) ? Real(tan(fValue.valueRadians())) : TanTable(fValue.valueRadians());
		}
        /** Tangent function.
            @param fValue
                Angle in radians
            @param useTables
                If true, uses lookup tables rather than
                calculation - faster but less accurate.
        */
		static inline Real Tan (Real fValue, bool useTables = false) {
			return (!useTables) ? Real(tan(fValue)) : TanTable(fValue);
		}

		static inline Real DegreesToRadians(Real degrees) { return degrees * Angle::fDeg2Rad; }
        static inline Real RadiansToDegrees(Real radians) { return radians * Angle::fRad2Deg; }

       /** These functions used to set the assumed angle units (radians or degrees) 
            expected when using the Angle type.
       @par
            You can set this directly after creating a new Root, and also before/after resource creation,
            depending on whether you want the change to affect resource files.
       */
       static void setAngleUnit(AngleUnit unit);
       /** Get the unit being used for angles. */
       static AngleUnit getAngleUnit(void);

       /** Convert from the current AngleUnit to radians. */
       static Real AngleUnitsToRadians(Real units);
       /** Convert from radians to the current AngleUnit . */
       static Real RadiansToAngleUnits(Real radians);
       /** Convert from the current AngleUnit to degrees. */
       static Real AngleUnitsToDegrees(Real units);
       /** Convert from degrees to the current AngleUnit. */
       static Real DegreesToAngleUnits(Real degrees);




  //      /** Build a reflection matrix for the passed in plane. */
  //      static Matrix4 buildReflectionMatrix(const Plane& p);
  //      /** Calculate a face normal, including the w component which is the offset from the origin. */
  //      static Vector4 calculateFaceNormal(const Vector3& v1, const Vector3& v2, const Vector3& v3);
  //      /** Calculate a face normal, no w-information. */
  //      static Vector3 calculateBasicFaceNormal(const Vector3& v1, const Vector3& v2, const Vector3& v3);
  //      /** Calculate a face normal without normalize, including the w component which is the offset from the origin. */
  //      static Vector4 calculateFaceNormalWithoutNormalize(const Vector3& v1, const Vector3& v2, const Vector3& v3);


		///** Generates a value based on the Gaussian (normal) distribution function
		//	with the given offset and scale parameters.
		//*/
		//static Real gaussianDistribution(Real x, Real offset = 0.0f, Real scale = 1.0f);

		//static Matrix4 makeViewMatrix(const Vector3& position, const Quaternion& orientation, 
		//	const Matrix4* reflectMatrix = 0);

		///** Get a bounding radius value from a bounding box. */
		//static Real boundingRadiusFromAABB(const AxisAlignedBox& aabb);
    };

}

namespace Glare {

	// these functions could not be defined within the class definition of class
	// Radian because they required class Degree to be defined
	inline Radian::Radian ( const Degree& d ) : mRad(d.valueRadians()) {
	}
	inline Radian& Radian::operator = ( const Degree& d ) {
		mRad = d.valueRadians(); return *this;
	}
	inline Radian Radian::operator + ( const Degree& d ) const {
		return Radian ( mRad + d.valueRadians() );
	}
	inline Radian& Radian::operator += ( const Degree& d ) {
		mRad += d.valueRadians();
		return *this;
	}
	inline Radian Radian::operator - ( const Degree& d ) const {
		return Radian ( mRad - d.valueRadians() );
	}
	inline Radian& Radian::operator -= ( const Degree& d ) {
		mRad -= d.valueRadians();
		return *this;
	}

	// these functions must be defined down here, because they rely on the
	// angle unit conversion functions in class Math:

	inline Real Radian::valueDegrees() const
	{
		return AngleUtils::RadiansToDegrees ( mRad );
	}

	inline Real Radian::valueAngleUnits() const
	{
		return AngleUtils::RadiansToAngleUnits ( mRad );
	}

	inline Real Degree::valueRadians() const
	{
		return AngleUtils::DegreesToRadians ( mDeg );
	}

	inline Real Degree::valueAngleUnits() const
	{
		return AngleUtils::DegreesToAngleUnits ( mDeg );
	}

	inline Angle::operator Radian() const
	{
		return Radian(AngleUtils::AngleUnitsToRadians(mAngle));
	}

	inline Angle::operator Degree() const
	{
		return Degree(AngleUtils::AngleUnitsToDegrees(mAngle));
	}

	inline Radian operator * ( Real a, const Radian& b )
	{
		return Radian ( a * b.valueRadians() );
	}

	inline Radian operator / ( Real a, const Radian& b )
	{
		return Radian ( a / b.valueRadians() );
	}

	inline Degree operator * ( Real a, const Degree& b )
	{
		return Degree ( a * b.valueDegrees() );
	}

	inline Degree operator / ( Real a, const Degree& b )
	{
		return Degree ( a / b.valueDegrees() );
	}

}
